#include "conf.h"

#include <QFile>
#include <QDateTime>

ConfigMenuItem::~ConfigMenuItem() {}

ConfigMenuItem_Key::ConfigMenuItem_Key(QString label, QString k1, QString k2,
        QString k3, const Place* p)
  : label(label), k1(k1), k2(k2), k3(k3), p(p) {}
void ConfigMenuItem_Key::type() {}

ConfigMenuItem_Cmd::ConfigMenuItem_Cmd(QString label, QString cmd)
  : label(label), cmd(cmd) {}
void ConfigMenuItem_Cmd::type() {}

ConfigMenuItem_NoAction::ConfigMenuItem_NoAction(QString label)
  : label(label) {}
void ConfigMenuItem_NoAction::type() {}




ConfigEffectItem::~ConfigEffectItem() {}

ConfigEffectItem_Key::ConfigEffectItem_Key(QString k1, QString k2, QString k3, const Place* p)
  : k1(k1), k2(k2), k3(k3), p(p) {}
void ConfigEffectItem_Key::type() {}

ConfigEffectItem_SKey::ConfigEffectItem_SKey(QString m, QString b1, QString k1,
        QString b2, QString k2)
  : m(m), b1(b1), k1(k1), b2(b2), k2(k2) {}
void ConfigEffectItem_SKey::type() {}

ConfigEffectItem_Cmd::ConfigEffectItem_Cmd(QString cmd) : cmd(cmd) {}
void ConfigEffectItem_Cmd::type() {}

ConfigEffectItem_Menu::ConfigEffectItem_Menu(QString menuName) : menuName(menuName) {}
void ConfigEffectItem_Menu::type() {}

ConfigEffectItem_Gestures::ConfigEffectItem_Gestures(QString menuName, QString cursorSetName)
  : menuName(menuName), cursorSetName(cursorSetName) {}
void ConfigEffectItem_Gestures::type() {}


Config::Config() {
  data = new configdata;
  indent = 0;
}
void Config::useConfig(configdata* otherdata) {
  this->data = otherdata;
}
Config::~Config() {
  cleanData();
  delete data;
}

void Config::cleanData() {
  data->cursors.clear();
  data->effects.clear();
  data->menus.clear();
}

void Config::createBlankConfig() {
  cleanData();
  delete data;
  
  data = new configdata;
}

void Config::addCursorSet(QString setName, QStringList cursors) {
  string sName = setName.toStdString();
  vector<string> v;
  
  for (int i = 0; i < cursors.count(); i++)
    v.push_back(cursors[i].toStdString());
  
  data->cursors[sName] = v;
}

void Config::deleteCursorSet(QString setName) {
  string sName = setName.toStdString();
  
  data->cursors.erase(sName);
}

void Config::renameCursorSet(QString oldSetName, QString newSetName) {
  string oldSName = oldSetName.toStdString();
  string newSName = newSetName.toStdString();
  
  data->cursors[newSName] = data->cursors[oldSName];
  data->cursors.erase(oldSName);
}

QStringList Config::cursors(QString setName) {
  string sName = setName.toStdString();
  QStringList ret;
  
  if (data->cursors.count(sName) > 0) {
    for (unsigned int i = 0; i < data->cursors[sName].size(); i++) {
      ret.append(data->cursors[sName][i].c_str());
    }
  }
  return ret;
}

QStringList Config::cursorSets() {
  QStringList ret;
  
  cursor_map& cm = data->cursors;
  cursor_map::const_iterator it;
  for (it = cm.begin(); it != cm.end(); it++)
    ret.append(it->first.c_str());
  
  return ret;
}

void Config::setMenuItem_Key(QString menuName, QString label,
        QStringList keys, QString place=NULL, int pos=-1) {
  string mName = menuName.toStdString();
  string l = label.toStdString();
  
  menu_item* mi = new menu_item;
  
  mi->label = str2char(l);
  
  keylist k = getKeys(keys);
  KeySym k1 = k.k1;
  KeySym k2 = k.k2;
  KeySym k3 = k.k3;
  
  const Place* p = getPlace(place);
  if (p != NULL)
    mi->action = new Key_Action(*p, k1, k2, k3);
  else {
    mi->action = new Key_Action(k1, k2, k3);
  }
  
  addMenuItem(mName, mi, pos);
}

void Config::setMenuItem_Cmd(QString menuName, QString label, QString cmd, int pos=-1) {
  string mName = menuName.toStdString();
  
  menu_item* mi = new menu_item;
  
  mi->label = str2char(label.toStdString());
  mi->action = new System_Action(str2char(cmd.toStdString()));
  
  addMenuItem(mName, mi, pos);
}

void Config::setMenuItem_NoAction(QString menuName, QString label, int pos=-1) {
  string mName = menuName.toStdString();
  
  menu_item* mi = new menu_item;
  
  mi->label = str2char(label.toStdString());
  mi->action = new No_Action();
  
  addMenuItem(mName, mi, pos);
}

QList<QPair<QString, QString> > Config::menuItems(QString menuName) {
  string mName = menuName.toStdString();
  QList<QPair<QString, QString> > ret;
  
  if (data->menus.count(mName) == 1) {
    vector<menu_item*>& list = data->menus[mName];
    vector<menu_item*>::const_iterator i;
    for (i = list.begin(); i != list.end(); i++) {
      QPair<QString, QString> pair;
      Key_Action* kact;
      System_Action* cmdact;
      No_Action* noact;
      
      pair.first = (*i)->label;
      
      if ((kact = dynamic_cast<Key_Action*>((*i)->action))) {
        // Key action
        const Place* p = kact->place();
        QString k1 = XKeysymToString(kact->key1());
        QString k2 = XKeysymToString(kact->key2());
        QString k3 = XKeysymToString(kact->key3());
        
        QString action = "Press key: ";
        action += (k2.isNull() ? "" : (k2 + "+"));
        action += (k3.isNull() ? "" : (k3 + "+"));
        action += k1;
        if (p) {
          action += ". Move cursor to ";
          action += p->name().c_str();
          action += " corner.";
        }
        pair.second = action;
      } else if ((cmdact = dynamic_cast<System_Action*>((*i)->action))) {
        // Command action
        QString cmd = cmdact->command();
        pair.second = QString("Run command: ") + cmd;
      } else if ((noact = dynamic_cast<No_Action*>((*i)->action))) {
        // No action
        pair.second = "No action";
      } else continue;
      
      ret.append(pair);
    }
  }
  
  return ret;
}

ConfigMenuItem* Config::menuItem(QString menuName, int pos) {
  string mName = menuName.toStdString();
  if (data->menus.count(mName) == 1) {
    vector<menu_item*>& list = data->menus[mName];
    if (list.size() > pos) {
      menu_item* mi = list[pos];
      
      if (Key_Action* kact = dynamic_cast<Key_Action*>(mi->action)) {
        const Place* p = kact->place();
        QString k1 = XKeysymToString(kact->key1());
        QString k2 = XKeysymToString(kact->key2());
        QString k3 = XKeysymToString(kact->key3());
        
        ConfigMenuItem_Key* key = new ConfigMenuItem_Key(mi->label, k1, k2, k3, p);
        return key;
      } else if (System_Action* sact = dynamic_cast<System_Action*>(mi->action)) {
        return new ConfigMenuItem_Cmd(mi->label, sact->command());
      } else if (dynamic_cast<No_Action*>(mi->action)) {
        return new ConfigMenuItem_NoAction(mi->label);
      }
    }
  }
  
  return NULL;
}

QStringList Config::menus() {
  QStringList ret;
  
  menu_map& mm = data->menus;
  menu_map::const_iterator it;
  for (it = mm.begin(); it != mm.end(); it++)
    ret.append(it->first.c_str());
  
  return ret;
}

void Config::deleteMenuItem(QString menuName, int pos) {
  string mName = menuName.toStdString();
  if (data->menus.count(mName) == 1) {
    vector<menu_item*>& list = data->menus[mName];
    if (list.size() > pos)
      list.erase(list.begin() + pos);
  }
}

void Config::renameMenu(QString oldMenuName, QString newMenuName) {
  string oldname = oldMenuName.toStdString();
  string newname = newMenuName.toStdString();
  
  data->menus[newname] = data->menus[oldname];
  data->menus.erase(oldname);
}

void Config::deleteMenu(QString menu) {
  string name = menu.toStdString();
  
  data->menus.erase(name);
}



/********* EFFECTS *********/

Trigger* Config::createTrigger(QString button, QString place, QString fclass=NULL) {
  unsigned int b = 0;
  if (button == "left") 
    b = 1;
  else if (button == "middle")
    b = 2;
  else if (button == "right")
    b = 3;
  else if (button == "scroll up")
    b = 4;
  else if (button =="scroll down")
    b = 5;
  else
    return NULL;
  
  // Trigger() doesn't change the Place parameter, but is not declared const
  Place* p = const_cast<Place*>(getPlace(place));
  if (p == NULL)
    // throw exception here
    return NULL;
  
  char* wclass;
  if (fclass != NULL) {
    wclass = str2char(fclass.toStdString());
    return new Trigger(b, *p, wclass);
  } else
    return new Trigger(b, *p, NULL);
}

void Config::setEffect_Key(Trigger* trigger, QStringList keys, QString place, int pos) {
  effect* ef = new effect;
  ef->t = trigger;
  
  keylist k = getKeys(keys);
  KeySym k1 = k.k1;
  KeySym k2 = k.k2;
  KeySym k3 = k.k3;
  
  const Place* p = getPlace(place);
  if (p != NULL)
    ef->a = new Key_Action(*p, k1, k2, k3);
  else {
    ef->a = new Key_Action(k1, k2, k3);
  }
  
  addEffect(ef, pos);
}

void Config::setEffect_SKey(Trigger* trigger, QStringList keys, QStringList buttons, int pos) {
  effect* ef = new effect;
  ef->t = trigger;
  
  keylist k = getKeys(keys);
  KeySym k1 = k.k1;
  KeySym k2 = k.k2;
  KeySym k3 = k.k3;
  
  unsigned int b1 = getButtonFromString(buttons[0]);
  unsigned int b2 = getButtonFromString(buttons[1]);
  
  ef->a = new Stateful_Key(k1, b1, k2, b2, k3);
  
  addEffect(ef, pos);
}

void Config::setEffect_Cmd(Trigger* trigger, QString cmd, int pos) {
  effect* ef = new effect;
  ef->t = trigger;
  
  char* command = str2char(cmd.toStdString());
  
  ef->a = new System_Action(command);
  
  addEffect(ef, pos);
}

void Config::setEffect_Menu(Trigger* trigger, QString menuName, int pos) {
  effect* ef = new effect;
  ef->t = trigger;
  
  char* mName = str2char(menuName.toStdString());
  ef->a = new Vertical_Menu(getMenu(menuName), ARRAY_LENGTH(menuName),
          mName);
  
  addEffect(ef, pos);
}

void Config::setEffect_Gesture(Trigger* trigger, QString menuName, QString cursorsName, int pos) {
  effect* ef = new effect;
  ef->t = trigger;
  
  menu_item* menu = getMenu(menuName);
  char* mName = str2char(menuName.toStdString());
  char* cName = str2char(cursorsName.toStdString());
  ef->a = new Gesture_Menu(trigger->place(), menu, ARRAY_LENGTH(menu),
          data->cursors[cursorsName.toStdString()],
          mName, cName);
  
  addEffect(ef, pos);
}

void Config::deleteEffectItem(int pos) {
  vector<effect*>& list = data->effects;
  if (list.size() > pos)
    list.erase(list.begin() + pos);
}

QList<QStringList> Config::effects() {
  QList<QStringList> ret;
  
  vector<effect*> effects = data->effects;
  vector<effect*>::const_iterator it;
  for (it = effects.begin(); it != effects.end(); it++) {
    QStringList list;
    Trigger* trigger = (*it)->t;
    QString tstr = trigger->place().name().c_str();
    tstr += " corner. ";
    tstr += getStringFromButton(trigger->button());
    tstr += " button.";
    list.append(tstr);
    list.append(trigger->window_class());
    list.append(parseAction((*it)->a));
    
    ret.append(list);
  }
  
  return ret;
}

ConfigEffectItem* Config::effectItem(int pos) {
  vector<effect*>& list = data->effects;
  if (list.size() > pos) {
    effect* ef = list[pos];
    
    if (Key_Action* kact = dynamic_cast<Key_Action*>(ef->a)) {
      const Place* p = kact->place();
      QString k1 = XKeysymToString(kact->key1());
      QString k2 = XKeysymToString(kact->key2());
      QString k3 = XKeysymToString(kact->key3());
      
      ConfigEffectItem_Key* key = new ConfigEffectItem_Key(k1, k2, k3, p);
      return key;
    } else if (System_Action* sact = dynamic_cast<System_Action*>(ef->a)) {
      return new ConfigEffectItem_Cmd(sact->command());
    } else if (Stateful_Key* skact = dynamic_cast<Stateful_Key*>(ef->a)) {
      return new ConfigEffectItem_SKey(
              XKeysymToString(skact->modifier()),
              QString("%1").arg(skact->button1()),
              XKeysymToString(skact->key1()),
              QString("%1").arg(skact->button2()),
              XKeysymToString(skact->key2())
              );
    } else if (Vertical_Menu* vmact = dynamic_cast<Vertical_Menu*>(ef->a)) {
      return new ConfigEffectItem_Menu(vmact->name());
    } else if (Gesture_Menu* gmact = dynamic_cast<Gesture_Menu*>(ef->a)) {
      return new ConfigEffectItem_Gestures(gmact->menuName(), gmact->cursorSetName());
    }
  }
  
  return NULL;
}

QString Config::effectWindowClass(int pos) {
  vector<effect*>& list = data->effects;
  if (list.size() > pos) {
    effect* ef = list[pos];
    return ef->t->window_class();
  }
  return NULL;
}
QPair<QString, QString> Config::effectArea(int pos) {
  vector<effect*>& list = data->effects;
  QPair<QString, QString> ret;
  if (list.size() > pos) {
    effect* ef = list[pos];
    ret.first = getStringFromButton(ef->t->button());
    ret.second = ef->t->place().name().c_str();
    return ret;
  }
  
  return ret;
}

int Config::findEffectWithMenu(QString menuName) {
  vector<effect*>& list = data->effects;
  for (unsigned int i = 0; i < list.size(); i++) {
    if (Vertical_Menu* vm = dynamic_cast<Vertical_Menu*>(list[i]->a)) {
      if (QString(vm->name()) == menuName)
        return i;
    } else if (Gesture_Menu* gm = dynamic_cast<Gesture_Menu*>(list[i]->a)) {
      if (QString(gm->menuName()) == menuName)
        return i;
    }
  }
  return -1;
}

int Config::findEffectWithCursorSet(QString setName) {
  vector<effect*>& list = data->effects;
  for (unsigned int i = 0; i < list.size(); i++) {
    if (Gesture_Menu* gm = dynamic_cast<Gesture_Menu*>(list[i]->a)) {
      if (QString(gm->cursorSetName()) == setName)
        return i;
    }
  }
  return -1;
}

void Config::renameMenuInEffect(int pos, QString newName) {
  vector<effect*>& list = data->effects;
  if (list.size() > pos) {
    effect* ef = list[pos];
    if (Vertical_Menu* vm = dynamic_cast<Vertical_Menu*>(ef->a))
      vm->setName(newName.toStdString().c_str());
    else if (Gesture_Menu* gm = dynamic_cast<Gesture_Menu*>(ef->a))
      gm->setMenuName(newName.toStdString().c_str());
  }
}

void Config::renameCursorSetInEffect(int pos, QString newName) {
  vector<effect*>& list = data->effects;
  if (list.size() > pos) {
    effect* ef = list[pos];
    if (Gesture_Menu* gm = dynamic_cast<Gesture_Menu*>(ef->a))
      gm->setCursorSetName(newName.toStdString().c_str());
  }
}


/********* EMITTER *********/

void Config::setDestinationFile(QString filename) {
  this->filename = filename;
}

bool Config::destinationIsSet() {
  if (filename.trimmed().isEmpty()) return false;
  return true;
}

QString Config::destination() {
  return filename;
}

QString Config::writeConfig() {
  QFile file(filename);
  
  if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
    return file.errorString();
  
  QTextStream s(&file);
  
  // write the header
  s << "# File automatically written by EdgyConfigGUI" << endl <<
          "# Generated: " << QDateTime::currentDateTime().toString() <<
          endl << endl << "---" << endl;
  
  // write cursors section first
  cursor_map cMap = data->cursors;
  if (cMap.size() > 0) {
    s << "cursors :" << endl;
    incrIndent(4);
    
    cursor_map::const_iterator cit;
    for (cit = cMap.begin(); cit != cMap.end(); cit++) {
      s << addIndent() << "- name : " << cit->first.c_str() << endl;
      incrIndent(2);
      s << addIndent() << "items : [";
      for (unsigned int i = 0; i < cit->second.size(); i++) {
        s << cit->second[i].c_str();
        if (i != cit->second.size() - 1)
          s << ", ";
      }
      s << ']' << endl;
      incrIndent(-2);
    }
    incrIndent(-4);
    s << endl;
  }
  
  // then write the menus
  menu_map mMap = data->menus;
  if (mMap.size() > 0) {
    s << "menu items :" << endl;
    incrIndent(4);
    
    menu_map::const_iterator mit;
    for (mit = mMap.begin(); mit != mMap.end(); mit++) {
      s << addIndent() << "- name : " << mit->first.c_str() << endl;
      incrIndent(2);
      s << addIndent() << "items : " << endl;
      incrIndent(4);
      for (unsigned int i = 0; i < mit->second.size(); i++) {
        menu_item* mi = mit->second[i];
        s << addIndent() << "- label : " << mi->label << endl;
        incrIndent(2);
        s << addIndent() << "action : ";
        if (Key_Action* kact = dynamic_cast<Key_Action*>(mi->action)) {
          s << "key" << endl;
          if (kact->key2())
            s << addIndent() << "modifier1 : " << XKeysymToString(kact->key2()) << endl;
          if (kact->key3())
            s << addIndent() << "modifier2 : " << XKeysymToString(kact->key3()) << endl;
          s << addIndent() << "key : " << XKeysymToString(kact->key1()) << endl;
        } else if (System_Action* sact = dynamic_cast<System_Action*>(mi->action)) {
          s << "command" << endl;
          s << addIndent() << "command : " << sact->command() << endl;
        } else if (dynamic_cast<No_Action*>(mi->action)) {
          s << "no action" << endl;
        } else {
          return "Unknown action for menu " + QString(mit->first.c_str()) +\
                  " and item with label " + QString(mi->label);
        }
        incrIndent(-2);
      }
      incrIndent(-6);
      s << endl;
    }
    incrIndent(-4);
    s << endl;
  }
  
  // finally write the effects
  vector<effect*>& effects = data->effects;
  if (effects.size() > 0) {
    s << "effects : " << endl;
    incrIndent(4);
    
    vector<effect*>::const_iterator it;
    for (it = effects.begin(); it != effects.end(); it++) {
      Trigger* t = (*it)->t;
      Action* a = (*it)->a;
      
      s << addIndent() << "- trigger : {button : ";
      s << t->button();
      s << ", edge : ";
      s << t->place().name().c_str();
      const char* wc = t->window_class();
      if (wc && strcmp(wc, "") != 0)
        s << ", class : \"" << wc << "\"";
      s << "}" << endl;
      
      incrIndent(2);
      
      s << addIndent() << "action : ";
      if (Key_Action* kact = dynamic_cast<Key_Action*>(a)) {
        s << "key" << endl;
        if (kact->key2())
          s << addIndent() << "modifier1 : " << XKeysymToString(kact->key2()) << endl;
        if (kact->key3())
          s << addIndent() << "modifier2 : " << XKeysymToString(kact->key3()) << endl;
        s << addIndent() << "key : " << XKeysymToString(kact->key1()) << endl;
      } else if (System_Action* sact = dynamic_cast<System_Action*>(a)) {
        s << "command" << endl;
        s << addIndent() << "command : " << sact->command() << endl;
      } else if (Stateful_Key* skact = dynamic_cast<Stateful_Key*>(a)) {
        s << "stateful key" << endl;
        s << addIndent() << "modifier : " << XKeysymToString(skact->modifier()) << endl;
        s << addIndent() << "button1 : " << skact->button1() << endl;
        s << addIndent() << "key1 : " << XKeysymToString(skact->key1()) << endl;
        s << addIndent() << "button2 : " << skact->button2() << endl;
        s << addIndent() << "key2 : " << XKeysymToString(skact->key2()) << endl;
      } else if (Vertical_Menu* vmact = dynamic_cast<Vertical_Menu*>(a)) {
        s << "menu" << endl;
        s << addIndent() << "menu : " << vmact->name() << endl;
      } else if (Gesture_Menu* gmact = dynamic_cast<Gesture_Menu*>(a)) {
        s << "gestures" << endl;
        s << addIndent() << "area : " << t->place().name().c_str() << endl;
        s << addIndent() << "menu : " << gmact->menuName() << endl;
        s << addIndent() << "cursors : " << gmact->cursorSetName() << endl;
      }
      
      incrIndent(-2);
    }
    
    incrIndent(-4);
    s << endl;
  }
  
  return "";
}

inline int Config::indentLevel() {
  return indent;
}

inline void Config::incrIndent(int amount) {
  indent += amount;
}

inline QString Config::addIndent() {
  return QString(indent, ' ');
}



/********* HELPERS *********/

void Config::addMenuItem(string& menuName, menu_item* mi, int pos=-1) {
  if (pos == -1) {
    // add to the end of the list
    data->menus[menuName].push_back(mi);
  } else {
    // reset an existing item
    vector<menu_item*>& list = data->menus[menuName];
    list.erase(list.begin() + pos);
    list.insert(list.begin() + pos, mi);
  }
}

void Config::addEffect(effect* ef, int pos=-1) {
  if (pos == -1)
    // add to the end of the list
    data->effects.push_back(ef);
  else {
    // reset an existing item
    vector<effect*>& list = data->effects;
    list.erase(list.begin() + pos);
    list.insert(list.begin() + pos, ef);
  }
}

const Place* Config::getPlace(QString place) {
  if (place != NULL && !place.isNull()) {
    if (place == "top")
      return &TOP;
    else if (place == "top left")
      return &TOP_LEFT;
    else if (place == "top right")
      return &TOP_RIGHT;
    else if (place == "bottom")
      return &BOTTOM;
    else if (place == "bottom left")
      return &BOTTOM_LEFT;
    else if (place == "bottom right")
      return &BOTTOM_RIGHT;
    else if (place == "left")
      return &LEFT;
    else if (place == "right")
      return &RIGHT;
    else
      // todo: throw an exception here
      return NULL;
  }
  return NULL;
}

keylist Config::getKeys(QStringList keys) {
  KeySym k1 = NoSymbol;
  KeySym k2 = NoSymbol;
  KeySym k3 = NoSymbol;
  if (keys.count() == 1)
    k1 = XStringToKeysym(keys[0].toStdString().c_str());
  else if (keys.count() == 2) {
    k1 = XStringToKeysym(keys[0].toStdString().c_str());
    k2 = XStringToKeysym(keys[1].toStdString().c_str());
  } else if (keys.count() == 3) {
    k1 = XStringToKeysym(keys[0].toStdString().c_str());
    k2 = XStringToKeysym(keys[1].toStdString().c_str());
    k3 = XStringToKeysym(keys[2].toStdString().c_str());
  }
  
  keylist ret = {k1, k2, k3};
  return ret;
}

unsigned int Config::getButtonFromString(QString button) {
  if (button == "left")
    return 1;
  else if (button == "right")
    return 3;
  else if (button == "middle")
    return 2;
  else if (button == "scroll up")
    return 4;
  else if (button == "scroll down")
    return 5;
  
  return 1;
}

QString Config::getStringFromButton(unsigned int button) {
  switch (button) {
    case 1:
      return "left";
    case 2:
      return "middle";
    case 3:
      return "right";
    case 4:
      return "scroll up";
    case 5:
      return "scroll down";
    default:
      return "left"; // kind of dangerous. should throw exception
  }
}

menu_item* Config::getMenu(QString menuName) {
  string mname = menuName.toStdString();
  
  if (this->data->menus.count(mname) == 1) {
    vector<menu_item*> list = this->data->menus[mname];
    int size = list.size();
    menu_item* result = (menu_item*)malloc(sizeof(menu_item) * size);
    for (unsigned int i = 0; i < size; i++)
      result[i] = *(list[i]);
    return result;
  } else return NULL;
}

QString Config::parseAction(Action* ptr) {
  Key_Action* kact;
  System_Action* cmdact;
  No_Action* noact;
  Stateful_Key* skact;
  Vertical_Menu* vmact;
  Gesture_Menu* gmact;
  
  QString action;
  
  if ((kact = dynamic_cast<Key_Action*>(ptr))) {
    // Key action
    const Place* p = kact->place();
    QString k1 = XKeysymToString(kact->key1());
    QString k2 = XKeysymToString(kact->key2());
    QString k3 = XKeysymToString(kact->key3());
    
    action = "Press key: ";
    action += (k2.isNull() ? "" : (k2 + "+"));
    action += (k3.isNull() ? "" : (k3 + "+"));
    action += k1;
    if (p) {
      action += ". Move cursor to ";
      action += p->name().c_str();
      action += " corner.";
    }
  } else if ((cmdact = dynamic_cast<System_Action*>(ptr))) {
    // Command action
    action = "Run command: ";
    action += cmdact->command();
  } else if ((noact = dynamic_cast<No_Action*>(ptr))) {
    // No action
    action = "No action";
  } else if ((skact = dynamic_cast<Stateful_Key*>(ptr))) {
    // Stateful Key
    action = "Stateful key: hold ";
    action += XKeysymToString(skact->modifier());
    action += "; " + getStringFromButton(skact->button1());
    action += " button -> ";
    action += XKeysymToString(skact->key1());
    action += "; " + getStringFromButton(skact->button2());
    action += " button -> ";
    action += XKeysymToString(skact->key2());
  } else if ((vmact = dynamic_cast<Vertical_Menu*>(ptr))) {
    // Vertical menu
    action = "Vertical menu: ";
    action += vmact->name();
  } else if ((gmact = dynamic_cast<Gesture_Menu*>(ptr))) {
    action = "Gestural pie. Menu: ";
    action += gmact->menuName();
    action += ". Cursor set: ";
    action += gmact->cursorSetName();
  }
  
  return action;
}

